/*
 * Copyright (c) 2010, Jeffrey F. Cole
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice, this
 *  list of conditions and the following disclaimer.
 *
 *  Redistributions in binary form must reproduce the above copyright notice,
 *  this list of conditions and the following disclaimer in the documentation
 *  and/or other materials provided with the distribution.
 *
 *  Neither the name of the technologichron.net nor the names of its contributors
 *  may be used to endorse or promote products derived from this software
 *  without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.klm.datakeeper.smsbackup;

import android.content.Context;
import android.graphics.Typeface;
import android.os.Handler;
import android.text.InputType;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;

/**
 * A simple layout group that provides a numeric text area with two buttons to
 * increment or decrement the value in the text area. Holding either button
 * will auto increment the value up or down appropriately.
 *
 * @author Jeffrey F. Cole
 *
 */
public class NumberPicker extends LinearLayout {
  private final long mRepeatDelay;
  private final int mTextSize;

  private final float mMinimum;
  private final float mMaximum;
  private final float mIncrement;

  private Float value;

  Button decrement;
  Button increment;
  public EditText valueText;

  private Handler repeatUpdateHandler = new Handler();

  private boolean autoIncrement = false;
  private boolean autoDecrement = false;

  /**
   * This little guy handles the auto part of the auto incrementing feature.
   * In doing so it instantiates itself. There has to be a pattern name for
   * that...
   *
   * @author Jeffrey F. Cole
   *
   */
  class RepetetiveUpdater implements Runnable {
    public void run() {
      if( autoIncrement ){
        increment();
        repeatUpdateHandler.postDelayed( new RepetetiveUpdater(), mRepeatDelay );
      } else if( autoDecrement ){
        decrement();
        repeatUpdateHandler.postDelayed( new RepetetiveUpdater(), mRepeatDelay );
      }
    }
  }

  public NumberPicker( Context context, AttributeSet attrs ) {
    super(context, attrs);

    LayoutParams elementParams = new LinearLayout.LayoutParams( LayoutParams.WRAP_CONTENT,
                                                                LayoutParams.WRAP_CONTENT );
    this.setLayoutParams( elementParams );

    final String NS = "http://zegoggles.com/smssync";
    mMinimum = attrs.getAttributeFloatValue(NS, "minimumValue", 0f);
    mMaximum = attrs.getAttributeFloatValue(NS, "maximumValue", 999f);
    mIncrement = attrs.getAttributeFloatValue(NS, "increment", 1.0f);
    final float initialValue = attrs.getAttributeFloatValue(NS, "initialValue", 0f);

    mTextSize = attrs.getAttributeIntValue(NS, "textSize", 18);
    mRepeatDelay = attrs.getAttributeIntValue(NS, "repeatDelay", 50);


    // init the individual elements
    initDecrementButton( context );
    initValueEditText( context, initialValue );
    initIncrementButton( context );

    // Can be configured to be vertical or horizontal
    // Thanks for the help, LinearLayout!
    if( getOrientation() == VERTICAL ){
      addView( increment, elementParams );
      addView( valueText, elementParams );
      addView( decrement, elementParams );
    } else {
      addView( decrement, elementParams );
      addView( valueText, elementParams );
      addView( increment, elementParams );
    }
  }

  private Typeface getButtonTypeface() {
    return Typeface.MONOSPACE;
  }

  private void initIncrementButton( Context context){
    increment = new Button( context );
    increment.setTextSize( mTextSize );
    increment.setText( " + " );
    increment.setTypeface(getButtonTypeface());

    // Increment once for a click
    increment.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
              increment();
            }
        });

    // Auto increment for a long click
    increment.setOnLongClickListener(
        new View.OnLongClickListener(){
          public boolean onLongClick(View arg0) {
            autoIncrement = true;
            repeatUpdateHandler.post( new RepetetiveUpdater() );
            return false;
          }
        }
    );

    // When the button is released, if we're auto incrementing, stop
    increment.setOnTouchListener( new View.OnTouchListener() {
      public boolean onTouch(View v, MotionEvent event) {
        if( event.getAction() == MotionEvent.ACTION_UP && autoIncrement ){
          autoIncrement = false;
        }
        return false;
      }
    });
  }

  private void initValueEditText( Context context, float initialValue){

    value = initialValue;

    valueText = new EditText( context );
    valueText.setTextSize(mTextSize);

    // Since we're a number that gets affected by the button, we need to be
    // ready to change the numeric value with a simple ++/--, so whenever
    // the value is changed with a keyboard, convert that text value to a
    // number. We can set the text area to only allow numeric input, but
    // even so, a carriage return can get hacked through. To prevent this
    // little quirk from causing a crash, store the value of the internal
    // number before attempting to parse the changed value in the text area
    // so we can revert to that in case the text change causes an invalid
    // number
    valueText.setOnKeyListener(new View.OnKeyListener() {
      public boolean onKey(View v, int arg1, KeyEvent event) {
        float backupValue = value;
        try {
          value = Float.parseFloat( ((EditText)v).getText().toString() );
        } catch( NumberFormatException nfe ){
          value = backupValue;
        }
        return false;
      }
    });

    // Highlight the number when we get focus
    valueText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
      public void onFocusChange(View v, boolean hasFocus) {
        if( hasFocus ){
          //((EditText)v).selectAll();
        }
      }
    });

    valueText.setGravity( Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL );
    valueText.setText( value.toString() );
    valueText.setInputType( InputType.TYPE_CLASS_NUMBER );
  }

  private void initDecrementButton( Context context){
    decrement = new Button( context );
    decrement.setTextSize( mTextSize );
    decrement.setText( " - " );
    decrement.setTypeface(getButtonTypeface());


    // Decrement once for a click
    decrement.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
              decrement();
            }
        });


    // Auto Decrement for a long click
    decrement.setOnLongClickListener(
        new View.OnLongClickListener(){
          public boolean onLongClick(View arg0) {
            autoDecrement = true;
            repeatUpdateHandler.post( new RepetetiveUpdater() );
            return false;
          }
        }
    );

    // When the button is released, if we're auto decrementing, stop
    decrement.setOnTouchListener( new View.OnTouchListener() {
      public boolean onTouch(View v, MotionEvent event) {
        if( event.getAction() == MotionEvent.ACTION_UP && autoDecrement ){
          autoDecrement = false;
        }
        return false;
      }
    });
  }

  public void increment(){
    if( value < mMaximum ){
      value = value + mIncrement;
      valueText.setText( value.toString() );
    }
  }

  public void decrement(){
    if( value > mMinimum ){
      value = value - mIncrement;
      valueText.setText( value.toString() );
    }
  }

  public float getValue(){
    return value;
  }

  /** @noinspection UnusedDeclaration*/
  public void setValue( float value ){
    if( value > mMaximum ) value = mMaximum;
    if( value >= 0 ){
      this.value = value;
      valueText.setText( this.value.toString() );
    }
  }
}
